.. _глава_46_start:

Преобразования типов
====================

Переменные должны быть совместимы с выражениями, в которых они участвуют. Как уже
вероятно, очевидно из программ, которые мы видели до сих пор, D является
статически типизированный язык, что означает, что совместимость типов проверяется во время
во время компиляции.
Все выражения, которые мы до сих пор писали, всегда имели совместимые типы
потому что в противном случае код был бы отвергнут компилятором. Ниже приведен
пример кода с несовместимыми типами:

.. code-block:: d

    char[] slice;
    writeln(slice + 5); // ошибка компиляции


Компилятор отклоняет код из-за несовместимости типов char[] и int для
операции сложения:

.. code-block:: console

    Error: incompatible types for ((slice) + (5)): 'char[]' and 'int'

Type incompatibility does not mean that the types are different; different types
can indeed be used in expressions safely. For example, an int variable can safely
be used in place of a double value:

.. code-block:: d

    double sum = 1.25;
    int increment = 3;
    sum += increment;

Несмотря на то, что sum и increment относятся к разным типам, приведенный выше код является корректным
потому что увеличение переменной double на значение int является законным.

.. index::
    автоматические преобразования типов

Автоматические преобразования типов
------------------------------------

Автоматические преобразования типов также называются неявными преобразованиями типов.
Хотя в приведенном выше выражении double и int являются совместимыми типами, операция
операция сложения все равно должна быть вычислена как определенный тип на уровне микропроцессора
уровне микропроцессора. Как вы помните из главы "Типы с плавающей точкой" (стр. 42),
64-битный тип double шире (или больше), чем 32-битный тип int. Кроме того,
любое значение, помещающееся в int, также помещается в double.
Когда компилятор встречает выражение, в котором используются несовпадающие типы,
он сначала преобразует части выражения к общему типу, а затем производит вычисление
общее выражение. Автоматические преобразования, выполняемые
компилятором, направлены в ту сторону, которая позволяет избежать потери данных. Например, double может содержать
любое значение, которое может содержать int, но обратное не верно. Приведенная выше операция +=
может работать, потому что любое значение int может быть безопасно преобразовано в double.
Значение, которое было сгенерировано автоматически в результате преобразования, является
всегда является анонимной (и часто временной) переменной. Исходное значение не
меняется. Например, автоматическое преобразование при += выше не изменяет
тип инкремента; он всегда остается int. Скорее, со значением increment создается временное значение типа
double строится со значением инкремента. Преобразование, которое происходит
происходит в фоновом режиме, эквивалентно следующему коду:

.. code-block:: d

    {
        double an_anonymous_double_value = increment;
        sum += an_anonymous_double_value;
    }

Компилятор преобразует значение int во временное значение double и использует его
значение в операции. В этом примере временная переменная живет только во время
операции +=.
Автоматические преобразования не ограничиваются арифметическими операциями. Существуют
другие случаи, когда типы автоматически преобразуются в другие типы. До тех пор пока
преобразования корректны, компилятор использует преимущества преобразования типов, чтобы иметь возможность
использовать значения в выражениях. Например, для параметра int можно передать байтовое значение
параметр:

.. code-block:: d

    void func(int number)
    {
    // ...
    }

    void main()
    {
        byte smallValue = 7;
        func(smallValue); // автоматическое преобразование типа
    }

В приведенном выше коде сначала создается временное значение int, а затем вызывается функция
вызывается с этим значением.

.. index::
    целочисленные преобразования

**Целочисленные преобразования**

Значения типов, расположенных в левой части следующей таблицы, никогда не участвуют в
в арифметических выражениях как их фактические типы. Каждый тип сначала повышается до
который находится в правой части таблицы.

.. list-table:: Целочисленные преобразования
   :widths: 25 50
   :header-rows: 1
   :align: center

   * - From
     - To
   * - bool
     - int
   * - byte
     - int
   * - ubyte
     - int
   * - short
     - int
   * - ushort
     - int
   * - char
     - int
   * - wchar
     - int
   * - dchar
     - uint

Целочисленные повышения применяются и к значениям перечислений.
Причины целочисленных преобразований  исторические (когда правила пришли из языка
из C), так и тем, что естественным арифметическим типом для микропроцессора является
int. Например, хотя две следующие переменные обе являются ubyte, операция
операция сложения выполняется только после того, как оба значения по отдельности
переведены в int:

.. code-block:: d

    ubyte a = 1;
    ubyte b = 2;
    writeln(typeof(a + b).stringof); // the addition is not in ubyte

Вывод

.. code-block:: console

    int

Обратите внимание, что типы переменных a и b не меняются; только их значения
временно переводятся в int на время выполнения операции сложения.

.. index::
    арифметические преобразования

**Арифметические преобразования**

Существуют и другие правила преобразования, которые применяются для арифметических операций. В
в общем случае автоматические арифметические преобразования применяются в безопасном направлении: от
более узкого типа к более широкому. Хотя это правило легко запомнить и оно
правильным в большинстве случаев, правила автоматического преобразования очень сложны, и в случае
в случае преобразований от знакового к беззнаковому, несут в себе некоторый риск ошибок.

Правила арифметического преобразования следующие:

#. Если одно из значений является вещественным, то другое значение преобразуется в вещественное
#. Иначе, если одно из значений является double, то другое значение преобразуется в double
#. Иначе, если одно из значений является float, то другое значение преобразуется в float
#. Иначе, сначала применяются целочисленные преобразования в соответствии с таблицей выше, а затем
   затем выполняются следующие правила:

    #. Если оба типа одинаковы, то больше никаких действий не требуется
    #. Если оба типа являются знаковыми или беззнаковыми, то более узкое
       значение преобразуется в более широкий тип
    #. Если знаковый тип шире беззнакового, то беззнаковое значение
       преобразуется к знаковому типу
    #. В противном случае знаковый тип преобразуется в беззнаковый.

К сожалению, последнее правило может привести к тонким ошибкам:

.. code-block:: d

    int a = 0;
    int b = 1;
    size_t c = 0;
    writeln(a - b + c); // удивительный результат

Удивительно, но на выходе получается не -1, а size_t.max:

.. code-block:: console

    18446744073709551615

Хотя можно было бы ожидать, что (0 - 1 + 0) будет вычислено как -1, в соответствии с
вышеприведенным правилам, тип всего выражения - size_t, а не int; а поскольку size_t не может хранить отрицательные значения, результат переполняется и становится size_t.max.
не может содержать отрицательных значений, результат переполняется и становится size_t.max.

.. index::
    преобразования фрагментов

**Преобразования фрагментов**

Для удобства массивы фиксированной длины могут быть автоматически преобразованы в срезы
при вызове функции:

.. code-block:: d

    import std.stdio;

    void foo()
    {
        int[2] array = [1, 2];
        bar(array); // Passes fixed-length array as a slice
    }

    void bar(int[] slice)
    {
        writeln(slice);
    }

    void main()
    {
        foo();
    }


bar() получает срез всех элементов массива фиксированной длины и выводит его на печать:

.. code-block:: console

    [1, 2]

*Предупреждение: Локальный массив фиксированной длины не должен передаваться в качестве фрагмента, если функция
сохраняет фрагмент для последующего использования. Например, в следующей программе есть ошибка
потому что фрагмент, который сохраняет bar(), не будет действителен после выхода foo():*

.. code-block:: d

    import std.stdio;

    void foo()
    {
        int[2] array = [1, 2];
        bar(array); // передаём массив фиксированного размера как срез
    } // ← NOTE: за этой точкой 'array' уже не валиден

    int[] sliceForLaterUse;

    void bar(int[] slice)
    {
        // сохраняем  срез, который скоро станет недействительным
        sliceForLaterUse = slice;
        writefln("Inside bar : %s", sliceForLaterUse);
    }

    void main()
    {
        foo();
        /* BUG: Попытка доступа к массиву который уже не существует возможно */
        writefln("Inside main: %s", sliceForLaterUse);
    }

Результатом такой ошибки является неопределенное поведение. Выполнение примера может доказать, что
память, в которой раньше находились элементы массива, уже была использована для
других целей:

.. code-block:: console

    Inside bar : [1, 2] - астуальное значение
    Inside main: [4396640, 0] - неопределенное поведение

.. index::
    преобразования const

**Преобразования const**

Как мы уже видели ранее в главе "Параметры функций" (:ref:`Параметры функций<глава_35_start>`), ссылочные
типы могут быть автоматически преобразованы в const того же типа. Преобразование
в const безопасно, поскольку ширина типа не изменяется, а const - это
обещание не изменять переменную:


.. code-block:: d

    char[] parenthesized(const char[] text)
    {
        return "{" ~ text ~ "}";
    }

    void main()
    {
        char[] greeting;
        greeting ~= "hello world";
        parenthesized(greeting);
    }

Приведенное выше мутабельное приветствие автоматически преобразуется в const char[], когда оно
передается в parenthesized().
Как мы уже видели ранее, обратное преобразование не является автоматическим. Ссылка const
не преобразуется автоматически в мутабельную ссылку:

.. code-block:: d

    char[] parenthesized(const char[] text)
    {
        char[] argument = text; // ← ошибка компиляции
        // ...
    }

Обратите внимание, что в этой теме речь идет только о ссылках; поскольку переменные типов значений
копируются, то в любом случае невозможно повлиять на оригинал через копию:

.. code-block:: d

    const int totalCorners = 4;
    int theCopy = totalCorners; //тип значений - компилируется

Приведенное выше преобразование из const в mutable законно, потому что копия не является
ссылкой на оригинал.

.. index::
    неизменяемые преобразования


**Неизменяемые преобразования**

Поскольку immutable определяет, что переменная никогда не может измениться, ни преобразование
из immutable и в immutable не являются автоматическими:

.. code-block:: d

    string a = "hello"; // immutable characters
    char[] b = a; // ← ошибка компиляции
    string c = b; // ← ошибка компиляции

Как и в случае с преобразованиями const, эта тема также посвящена только ссылочным типам.
Поскольку переменные типов значений все равно копируются, преобразования к и от
immutable вполне допустимы:


.. code-block:: d

    immutable a = 10;
    int b = a; ///тип значений - компилируется

.. index::
    преобразования enum

**Преобразования enum**

Как мы уже видели в главе о перечислениях (:ref:`Enumeration<Enumeration>`), перечисления предназначены для определения именованных
констант:

.. code-block:: d

    enum Suit { spades, hearts, diamonds, clubs }

Помните, что поскольку выше значения не указаны явно, значения членов
перечисления начинаются с нуля и автоматически увеличиваются на единицу.
Соответственно, значение Suit.clubs равно 3.
Значения перечислений атомарно преобразуются в интегральные типы. Например, значение
Suit.hearts принимается за 1 в следующем вычислении, и результат
становится 11:

.. code-block:: d

    int result = 10 + Suit.hearts;
    assert(result == 11);

Обратное преобразование не является автоматическим: целочисленные значения не преобразуются автоматически
не преобразуются в соответствующие значения перечисления. Например, приведенная ниже переменная suit
должна превратиться в Suit.diamonds, но код не может быть скомпилирован:

.. code-block:: d

    Suit suit = 2; // ← ошибка компиляции

Как мы увидим ниже, преобразования из целых чисел в значения перечислений все еще возможны
но они должны быть явными.

.. index::
    преобразования bool

**Преобразования bool**

Хотя bool является естественным типом логических выражений, поскольку имеет только два значения,
его можно рассматривать как 1-битное целое число, и в некоторых случаях он действительно ведет себя как целое. false и
true автоматически преобразуются в 0 и 1 соответственно:


.. code-block:: d

    int a = false;
    assert(a == 0);
    int b = true;
    assert(b == 1);

Что касается литеральных значений, то обратное преобразование выполняется автоматически только для двух специальных
буквальных значений: 0 и 1 автоматически преобразуются в false и true,
соответственно:

.. code-block:: d

    bool a = 0;
    assert(!a); // false
    bool b = 1;
    assert(b);

Другие значения не могут быть автоматически преобразованы в bool:

.. code-block:: d

    bool b = 2; // ← ошибка компиляции

В некоторых утверждениях используются логические выражения: if, while и т. д. Для логических
выражений таких операторов можно использовать не только bool, но и большинство других типов
также. Нулевое значение автоматически преобразуется в false,  а ненулевые значения - в true.

.. code-block:: d

    int i;
    // ...
    if (i) // ← int value is being used as a logical expression
    {
        // ... 'i' is not zero
    }
    else
    {
        // ... 'i' is zero
    }

Аналогично, null ссылки автоматически преобразуются в false, а non-null
автоматически преобразуются в true. Это позволяет легко убедиться в том, что
что ссылка не является null, прежде чем использовать ее:

.. code-block:: d

    int[] a;
    // ...
    if (a) // ← automatic bool conversion
    {
        // ... not null; 'a' can be used ...
    }
    else
    {
        // ... null; 'a' cannot be used ...
    }

.. index::
    явные преобразования типов

Явные преобразования типов
--------------------------
Как мы видели выше, есть случаи, когда автоматические преобразования недоступны
недоступны:
* Преобразования из более широких типов в более узкие
* преобразования из const в mutable
* преобразования в неизменяемые
* Преобразования из целых чисел в значения перечислений
* и т.д.
Если известно, что такое преобразование безопасно, программист может явно запросить
преобразования типа одним из следующих методов:
* Синтаксис конструкции
* std.conv.to function
* функция std.exception.assumeUnique
* оператор приведения

**Синтаксис конструктора**

Синтаксис построения struct и class доступен и для других типов:

.. code-block:: d

    DestinationType(value)

Например, следующее преобразование делает из значения int значение double,
предположительно для сохранения дробной части операции деления:

.. code-block:: d

    int i;
    // ...
    const result = double(i) / 2;


**Шаблон to()**

Функция to(), которую мы уже использовали в основном для преобразования значений в
строку, на самом деле может использоваться и для многих других типов. Ее полный синтаксис выглядит следующим образом
следующий:

.. code-block:: d

    to!(DestinationType)(value)

Будучи шаблоном, to() может воспользоваться параметром шаблона с сокращением
нотации: Когда тип назначения состоит только из одной лексемы (обычно это
одно слово), его можно вызвать без первой пары круглых скобок:

.. code-block:: d

    to!DestinationType(value)

Следующая программа пытается преобразовать double значение в короткое и строковое
в int:

.. code-block:: d

    void main()
    {
        double d = -1.75;
        short s = d;  // ← ошибка компиляции
        int i = "42"; // ← ошибка компиляции
    }

Поскольку не каждое двойное значение может быть представлено в виде short и не каждая строка
может быть представлено как int, эти преобразования не являются автоматическими. Когда
программист знает, что преобразования на самом деле безопасны или что
потенциальные последствия приемлемы, то типы могут быть преобразованы с помощью to():

.. code-block:: d

    import std.conv;

    void main()
    {
        double d = -1.75;
        short s = to!short(d);
        assert(s == -1);
        int i = to!int("42");
        assert(i == 42);
    }

Обратите внимание, что поскольку short не может передавать дробные значения, преобразованное значение равно -1.
to() является безопасным: Она выбрасывает исключение, если преобразование невозможно.

**assumeUnique() для быстрых неизменяемых преобразований**

to() также может выполнять неизменяемые преобразования:

.. code-block:: d

    int[] slice = [ 10, 20, 30 ];
    auto immutableSlice = to!(immutable int[])(slice);

Чтобы гарантировать, что элементы immutableSlice никогда не изменятся, он
не может иметь общих элементов с slice. По этой причине функция to() создает
дополнительный срез над ним с неизменяемыми элементами . В противном случае изменения
элементов slice привели бы к изменению элементов immutableSlice.
Подобное поведение характерно и для свойства .idup массивов.
Мы можем убедиться, что элементы immutableSlice действительно являются копиями
элементов slice, посмотрев на адреса их первых элементов:

.. code-block:: d

    assert(&(slice[0]) != &(immutableSlice[0]));

Иногда это копирование не нужно и может заметно замедлить скорость работы программы
в некоторых случаях заметно замедлить скорость работы программы. В качестве примера рассмотрим следующую
функцию, которая принимает неизменяемый фрагмент:

.. code-block:: d

    void calculate(immutable int[] coordinates)
    {
    // ...
    }

    void main()
    {
        int[] numbers;
        numbers ~= 10;
        // ... various other modifications ...
        numbers[0] = 42;
        calculate(numbers); // ← ошибка компиляции
    }

Приведенная выше программа не может быть скомпилирована, потому что вызывающая сторона не передает
неизменяемый аргумент в calculate(). Как мы видели выше, неизменяемый фрагмент
может быть создан с помощью функции to():

.. code-block:: d

    import std.conv;
    // ...
    auto immutableNumbers = to!(immutable int[])(numbers);
    calculate(immutableNumbers); // ← now compiles

Однако если число numbers необходимо только для получения этого аргумента и никогда не будет
использоваться после вызова функции, копирование ее элементов в immutableNumbers
будет излишним. assumeUnique() делает элементы среза неизменяемыми
без копирования:

.. code-block:: d

    // ...
    auto immutableNumbers = assumeUnique(numbers);
    calculate(immutableNumbers);
    assert(numbers is null); // оригинал будет равен null


assumeUnique() возвращает новый срез, который обеспечивает неизменяемый доступ к существующим элементам
Также оригинальный срез становится равным null для предотвращения случайного доступа элемента и модификции их

assumeUnique() возвращает новый срез, который обеспечивает неизменяемый доступ к
существующим элементам. Она также делает исходный срез равным null, чтобы предотвратить случайное изменение элементов.

**Оператор cast**

И to(), и assumeUnique() используют оператор преобразования cast,
который также доступен программисту.
Оператор cast заключает тип назначения в круглые скобки:


.. code-block:: d

    cast(DestinationType)value

Оператор cast является мощным средством даже для преобразований, которые to() не может безопасно выполнить. На
Например, to() не работает для следующих преобразований во время выполнения:

.. code-block:: d

    Suit suit = to!Suit(7); // ← бросаем исключение
    bool b = to!bool(2); // ← бросаем исключение

.. code-block:: console

    std.conv.ConvException@phobos/std/conv.d(1778): Value (7) does not match any member value of enum 'Suit'

Иногда только программист может знать, соответствует ли целочисленное значение
соответствует допустимому значению перечисления или имеет смысл рассматривать целочисленное значение
как bool. Оператор приведения можно использовать, когда известно, что преобразование
правильным в соответствии с логикой программы:

.. code-block:: d

    // Возможно некорректно но допускается языком
    Suit suit = cast(Suit)7;
    bool b = cast(bool)2;
    assert(b);

Приведение является единственным вариантом при преобразовании в типы указателей и обратно:

.. code-block:: d

    void * v;
    // ...
    int * p = cast(int*)v;

Хотя это и редкость, некоторые интерфейсы библиотеки Си делают необходимым хранить значение указателя
как тип, не являющийся указателем. Если гарантируется, что при преобразовании будет сохранено
фактическое значение, cast может преобразовывать между типами указателей и не-указателей:

.. code-block:: d

    size_t savedPointerValue = cast(size_t)p;
    // ...
    int * p2 = cast(int*)savedPointerValue;

Итоги
-----

* Автоматические преобразования типов в основном безопасны: От
  более узкого типа к более широкому и от mutable к const.
* Однако преобразования к беззнаковым типам могут привести к неожиданным последствиям, поскольку
  беззнаковые типы не могут иметь отрицательных значений.
* Типы перечислений могут автоматически преобразовываться в целочисленные значения, но обратное
  преобразование не является автоматическим.
* false и true автоматически преобразуются в 0 и 1 соответственно. Аналогично,
  нулевые значения автоматически преобразуются в false, а ненулевые значения
  автоматически преобразуются в false, а ненулевые - в true.
* null ссылки автоматически преобразуются в false, а not-null
  ссылки автоматически преобразуются в true.
* Для явных преобразований можно использовать синтаксис построения.
* to() охватывает большинство явных преобразований.
* assumeUnique() преобразует в immutable без копирования.
* Оператор cast - самый мощный инструмент преобразования.
